/* Copyright (c) 2001 - 2013 OpenPlans - www.openplans.org. All rights reserved.
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geoserver.data.util;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.geotools.coverage.grid.GeneralGridGeometry;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.filter.text.ecql.ECQL;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.parameter.DefaultParameterDescriptor;
import org.geotools.referencing.CRS;
import org.geotools.util.Converters;
import org.opengis.filter.Filter;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterValue;
import org.opengis.parameter.ParameterValueGroup;


/**
 * DOCUMENT ME!
 *
 * @author $Author: Alessio Fabiani (alessio.fabiani@geo-solutions.it)
 * @author $Author: Simone Giannecchini (simone.giannecchini@geo-solutions.it)
 */
public class CoverageUtils {

    private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(CoverageUtils.class.toString());
    public static final int TRANSPARENT = 0;
    public static final int OPAQUE = 1;
    
    public static GeneralParameterValue[] getParameters(ParameterValueGroup params) {
        final List parameters = new ArrayList();
        final String readGeometryKey = AbstractGridFormat.READ_GRIDGEOMETRY2D.getName().toString();

        if ((params != null) && (params.values().size() > 0)) {
            List list = params.values();
            final Iterator it = list.iterator();
            while (it.hasNext()) {
                final ParameterValue val = (ParameterValue) it.next();

                if (val != null) {
                    final ParameterDescriptor descr = (ParameterDescriptor) val.getDescriptor();
                    final String _key = descr.getName().toString();

                    if ("namespace".equals(_key)) {
                        // skip namespace as it is *magic* and
                        // appears to be an entry used in all dataformats?
                        //
                        continue;
                    }


                    // IGNORING READ_GRIDGEOMETRY2D param
                    if (_key.equalsIgnoreCase(readGeometryKey)) {
                        continue;
                    }
                    final Object value = val.getValue();

                    parameters.add(new DefaultParameterDescriptor(_key, value.getClass(), null,value).createValue());
                }
            }

            return (!parameters.isEmpty())
            ? (GeneralParameterValue[]) parameters.toArray(new GeneralParameterValue[parameters.size()])
            : null;
        } else {
            return null;
        }
    }

    public static GeneralParameterValue[] getParameters(ParameterValueGroup params, Map values) {
        return getParameters(params, values, false);
    }

    public static GeneralParameterValue[] getParameters(ParameterValueGroup params, Map values,
        boolean readGeom) {
        final List<ParameterValue<?>> parameters = new ArrayList<ParameterValue<?>>();
        final String readGeometryKey = AbstractGridFormat.READ_GRIDGEOMETRY2D.getName().toString();

        if ((params != null) && (params.values().size() > 0)) {
            final List<GeneralParameterValue> elements = params.values();
            for (GeneralParameterValue elem: elements) {
                final ParameterValue<?> val = (ParameterValue<?>)elem;

                if (val != null) {
                    final ParameterDescriptor<?> descr = val.getDescriptor();
                    final String _key = descr.getName().toString();

                    if ("namespace".equals(_key)) {
                        // skip namespace as it is *magic* and
                        // appears to be an entry used in all dataformats?
                        //
                        continue;
                    }

                    // /////////////////////////////////////////////////////////
                    //
                    // request param for better management of coverage
                    //
                    // /////////////////////////////////////////////////////////
                    if (_key.equalsIgnoreCase(readGeometryKey) && !readGeom) {
                        // IGNORING READ_GRIDGEOMETRY2D param
                        continue;
                    }

                    // /////////////////////////////////////////////////////////
                    //
                    // format specific params
                    //
                    // /////////////////////////////////////////////////////////
                    final Object value = CoverageUtils.getCvParamValue(_key, val, values);
                    parameters.add(new DefaultParameterDescriptor(_key, descr.getValueClass(), null, value).createValue());
                    
                }
            }

            return (!parameters.isEmpty())? 
            		(GeneralParameterValue[]) parameters.toArray(new GeneralParameterValue[parameters.size()]): new GeneralParameterValue[0];
        } else {
            return new GeneralParameterValue[0];
        }
    }

    public static Map getParametersKVP(ParameterValueGroup params) {
        final Map parameters = new HashMap();
        final String readGeometryKey = AbstractGridFormat.READ_GRIDGEOMETRY2D.getName().toString();

        if ((params != null) && (params.values().size() > 0)) {
            final List list = params.values();
            final Iterator it = list.iterator();
            while (it.hasNext()) {
                final ParameterValue val = (ParameterValue) it.next();

                if (val != null) {
                    final ParameterDescriptor descr = (ParameterDescriptor) val.getDescriptor();

                    final String _key = descr.getName().toString();

                    if ("namespace".equals(_key)) {
                        // skip namespace as it is *magic* and
                        // appears to be an entry used in all dataformats?
                        //
                        continue;
                    }

                    // /////////////////////////////////////////////////////////
                    //
                    // request param for better management of coverage
                    //
                    // /////////////////////////////////////////////////////////
                    if (_key.equalsIgnoreCase(readGeometryKey)) {
                        // IGNORING READ_GRIDGEOMETRY2D param
                        continue;
                    }

                    Object value = val.getValue();
                    String text = "";

                    if (value == null) {
                        text = null;
                    } else if (value instanceof String) {
                        text = (String) value;
                    } else {
                        text = value.toString();
                    }

                    parameters.put(_key, (text != null) ? text : "");
                }
            }

            return parameters;
        } else {
            return parameters;
        }
    }

    /**
     * @param paramValues
     * @param key
     * @param param
     * @return
     */
    public static Object getCvParamValue(final String key, ParameterValue param,
        final List paramValues, final int index) {
        Object value = null;

        try {
            if (key.equalsIgnoreCase("crs")) {
                if ((getParamValue(paramValues, index) != null)
                        && (((String) getParamValue(paramValues, index)).length() > 0)) {
                    if ((paramValues.get(index) != null)
                            && (((String) paramValues.get(index)).length() > 0)) {
                        value = CRS.parseWKT((String) paramValues.get(index));
                    }
                } else {
                    LOGGER.info("Unable to find a crs for the coverage param, using EPSG:4326");
                    value = CRS.decode("EPSG:4326");
                }
            } else if (key.equalsIgnoreCase("envelope")) {
                if ((getParamValue(paramValues, index) != null)
                        && (((String) getParamValue(paramValues, index)).length() > 0)) {
                    String tmp = (String) getParamValue(paramValues, index);

                    if ((tmp.indexOf("[") > 0) && (tmp.indexOf("]") > tmp.indexOf("["))) {
                        tmp = tmp.substring(tmp.indexOf("[") + 1, tmp.indexOf("]")).trim();
                        tmp = tmp.replaceAll(",", "");

                        String[] strCoords = tmp.split(" ");
                        double[] coords = new double[strCoords.length];

                        if (strCoords.length == 4) {
                            for (int iT = 0; iT < 4; iT++) {
                                coords[iT] = Double.parseDouble(strCoords[iT].trim());
                            }

                            value = (org.opengis.geometry.Envelope) new GeneralEnvelope(new double[] {
                                        coords[0], coords[1]
                                    }, new double[] { coords[2], coords[3] });
                        }
                    }
                }
            } else {
                Class[] clArray = { getParamValue(paramValues, index).getClass() };
                Object[] inArray = { getParamValue(paramValues, index) };
                value = param.getValue().getClass().getConstructor(clArray).newInstance(inArray);
            }

            // Intentionally generic exception catched
        } catch (Exception e) {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
            }
            
            value = null;

            // errors.add("paramValue[" + i + "]",
            // new ActionError("error.dataFormatEditor.param.parse", key,
            // getParamValue(i).getClass(), e));
        }

        return value;
    }

    private static String getParamValue(final List paramValues, final int index) {
        return (String) paramValues.get(index);
    }

    /**
     * @param params
     * @param key
     * @param param
     * @return
     */
    public static Object getCvParamValue(final String key, ParameterValue param, final Map params) {
        Object value = null;

        try {
            if (key.equalsIgnoreCase("crs")) {
                if ((params.get(key) != null) && (((String) params.get(key)).length() > 0)) {
                    value = CRS.parseWKT((String) params.get(key));
                } else {
                    LOGGER.info("Unable to find a crs for the coverage param, using EPSG:4326");
                    value = CRS.decode("EPSG:4326");
                }
            } else if (key.equalsIgnoreCase("envelope")) {
                if ((params.get(key) != null) && (((String) params.get(key)).length() > 0)) {
                    String tmp = (String) params.get(key);

                    if ((tmp.indexOf("[") > 0) && (tmp.indexOf("]") > tmp.indexOf("["))) {
                        tmp = tmp.substring(tmp.indexOf("[") + 1, tmp.indexOf("]")).trim();
                        tmp = tmp.replaceAll(",", "");

                        String[] strCoords = tmp.split(" ");
                        double[] coords = new double[strCoords.length];

                        if (strCoords.length == 4) {
                            for (int iT = 0; iT < 4; iT++) {
                                coords[iT] = Double.parseDouble(strCoords[iT].trim());
                            }

                            value = (org.opengis.geometry.Envelope) new GeneralEnvelope(new double[] {
                                        coords[0], coords[1]
                                    }, new double[] { coords[2], coords[3] });
                        }
                    }
                }
            } else if (key.equalsIgnoreCase(AbstractGridFormat.READ_GRIDGEOMETRY2D.getName()
                                                                                      .toString())) {
                if ((params.get(key) != null) && params.get(key) instanceof String
                        && (((String) params.get(key)).length() > 0)) {
                    String tmp = (String) params.get(key);

                    if ((tmp.indexOf("[") > 0) && (tmp.indexOf("]") > tmp.indexOf("["))) {
                        tmp = tmp.substring(tmp.indexOf("[") + 1, tmp.indexOf("]")).trim();
                        tmp = tmp.replaceAll(",", "");

                        String[] strCoords = tmp.split(" ");
                        double[] coords = new double[strCoords.length];

                        if (strCoords.length == 4) {
                            for (int iT = 0; iT < 4; iT++) {
                                coords[iT] = Double.parseDouble(strCoords[iT].trim());
                            }

                            value = (org.opengis.geometry.Envelope) new GeneralEnvelope(new double[] {
                                        coords[0], coords[1]
                                    }, new double[] { coords[2], coords[3] });
                        }
                    }
                } else if ((params.get(key) != null)
                        && params.get(key) instanceof GeneralGridGeometry) {
                    value = params.get(key);
                }
            } else {
                final Class<? extends Object> target = param.getDescriptor().getValueClass();
                if (key.equalsIgnoreCase("InputTransparentColor")
                        || key.equalsIgnoreCase("OutputTransparentColor")) {
                    if (params.get(key) != null) {
                        value = Color.decode((String) params.get(key));
                    } else {
                        Class[] clArray = { Color.class };
                        Object[] inArray = { params.get(key) };
                        value = target.getConstructor(clArray).newInstance(inArray);
                    }
                }else if (key.equalsIgnoreCase("BackgroundValues")) {
                    if (params.get(key) != null) {
                        String temp = (String) params.get(key);
                        String[] elements = temp.split(",");
                        final double[] backgroundValues = new double[elements.length];
                        for(int i=0;i<elements.length;i++)
                        	backgroundValues[i]=Double.valueOf(elements[i]);
                        value=backgroundValues;
                        
                    } 
                } else if (key.equalsIgnoreCase("InputImageThresholdValue")) {
                    if (params.get(key) != null) {
                        String temp = (String) params.get(key);
                        value=Double.valueOf(temp);
                        
                    } 
                } else if (key.equalsIgnoreCase("Filter")) {
    	            	Object sfilter=params.get(key);
    	            	if (sfilter!=null){
    	            		if (sfilter instanceof String){
    	            			value=ECQL.toFilter((String)sfilter);
    	            		} else if (sfilter instanceof Filter){
    	            			value=(Filter)sfilter;
    	            		}
    	        		} else {
    	        			value=param.getValue();
    	        		}
            		 
                } else {
                    value = params.get(key);
                    if(value != null) {
                        Object converted = Converters.convert(value, target);
                        if(converted == null) {
                            throw new RuntimeException("Failed to convert " + value + " to " + target.getName());
                        } else {
                            value = converted;
                        }
                    }
                }
            }
        } catch (Exception e) {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
            }
            value = param.getValue();
        }

        return value;
    }
    
    
    
    /**
     * Merges the provided parameter in the read parameters, provided it's included in the specified
     * descriptors with one of the aliases
     * @param parameterDescriptors The parameter descriptors of the reader
     * @param readParameters The current set of reader parameters
     * @param value
     * @param parameterAliases
     * @return
     */
    public static GeneralParameterValue[] mergeParameter(List<GeneralParameterDescriptor> parameterDescriptors, 
            GeneralParameterValue[] readParameters, Object value, String... parameterAliases) {
        // setup a param name alias set
        Set<String> aliases = new HashSet<String>(Arrays.asList(parameterAliases));
        
        // scan all the params looking for the one we want to add
        for (GeneralParameterDescriptor pd : parameterDescriptors) {
            // in case of match of any alias add a param value to the lot
            if (aliases.contains(pd.getName().getCode())) {
                final ParameterValue pv = (ParameterValue) pd.createValue();
                pv.setValue(value);

                // add to the list
                GeneralParameterValue[] readParametersClone = new GeneralParameterValue[readParameters.length + 1];
                System.arraycopy(readParameters, 0, readParametersClone, 0,
                        readParameters.length);
                readParametersClone[readParameters.length] = pv;
                readParameters = readParametersClone;

                // leave
                break;
            }
        }
        
        return readParameters;
    }
     
}
